/*
   Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
   Original sources are available at www.latestbit.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include "cpputils/CppUtils.hpp"
#include "cpputils/dynmod/ModulesFactory.hpp"
#include "cpputils/dynmod/ModuleImpl.hpp"

namespace cpputils {
	namespace dynmod {

		// --------------------------------------------------------------------------------------------------
		ModulesFactory::ModulesFactory() {
		}

		// --------------------------------------------------------------------------------------------------
		ModulesFactory::~ModulesFactory() {
			purgeAllModules();
		}

		// --------------------------------------------------------------------------------------------------
		ModulesFactory ModulesFactory::m_oInstance;

		ModulesFactory* ModulesFactory::getInstance() {
			return &m_oInstance;
		}

		// --------------------------------------------------------------------------------------------------
		std::string ModulesFactory::getLibraryNameForModule(
				const std::string& sModuleName,
				const std::string& sLibraryName) {
			std::string sResult;
			if(sLibraryName.empty()) {
				sResult= "lib"+sModuleName+
				#ifdef _DEBUG
					"-d"+
				#endif
					OS_SHARED_LIBRARY_POSTFIX;
			}
			else
				sResult = sLibraryName;
			return sResult;
		}

		// --------------------------------------------------------------------------------------------------
		IModule::SharedPtr ModulesFactory::loadModuleFromLibrary(
				const std::string& sModuleName,
				const std::string& sVersion,
				const std::string& sDefLibraryName) {
			std::string sLibraryName = getLibraryNameForModule(sModuleName, sDefLibraryName);
			ModuleImpl* poImpl = NULL;
			dlhandle_t hModule;
	#ifdef WIN32
			hModule = LoadLibraryA(sLibraryName.c_str());
	#else
			hModule = dlopen(sLibraryName.c_str(), RTLD_NOW);
	#endif
			try {
				if(hModule==NULL) {
	#ifdef WIN32
					throw std::invalid_argument("Unable to load module: "+getLastOSErrorString());
	#else
					throw std::invalid_argument("Unable to load module: "+std::string(dlerror()));
	#endif
				}
	#ifdef WIN32
				PtrDynmodInitFunc pfInitFunc = (PtrDynmodInitFunc) GetProcAddress(hModule, DYNMOD_INIT_FUNC_NAME);
	#else
				PtrDynmodInitFunc pfInitFunc = (PtrDynmodInitFunc) dlsym ( hModule, DYNMOD_INIT_FUNC_NAME );
	#endif
				if(!pfInitFunc)
					throw std::logic_error("Unable to find init function for module "+sLibraryName);
				utils::Version oVersion( sVersion );
				ActivationResult nResult = pfInitFunc( oVersion );
				if(nResult != ACTIVATE_TRANSPORT_MODULE_SUCCESS) {
					throw std::logic_error( (boost::format("Unable to init module. Code: %1%") %nResult).str() ) ;
				}

	#ifdef WIN32
				PtrDynmodFinalizeFunc pfFinFunc = (PtrDynmodFinalizeFunc) GetProcAddress(hModule, DYNMOD_FINALIZE_FUNC_NAME);
	#else
				PtrDynmodFinalizeFunc pfFinFunc = (PtrDynmodFinalizeFunc) dlsym ( hModule, DYNMOD_FINALIZE_FUNC_NAME );
	#endif
				if(!pfFinFunc)
					throw std::logic_error("Unable to find finalize function for module "+sLibraryName);

	#ifdef WIN32
				PtrDynmodCreateInstanceFunc pfCreateFunc = (PtrDynmodCreateInstanceFunc) GetProcAddress(hModule, DYNMOD_CREATEINSTANCE_FUNC_NAME);
	#else
				PtrDynmodCreateInstanceFunc pfCreateFunc = (PtrDynmodCreateInstanceFunc) dlsym ( hModule, DYNMOD_CREATEINSTANCE_FUNC_NAME );
	#endif
				if(!pfCreateFunc)
					throw std::logic_error("Unable to find instance factory function for module "+sLibraryName);

				poImpl = new ModuleImpl(sModuleName,this, pfInitFunc, pfFinFunc, pfCreateFunc, hModule );
			}
			catch(std::exception ex) {
				if(hModule!=NULL) {
	#ifdef WIN32
					FreeLibrary(hModule);
	#else
					dlclose(hModule);
	#endif
				}
				throw;
			}
			return IModule::SharedPtr ( poImpl );
		}

		// --------------------------------------------------------------------------------------------------
		IModule::SharedPtr ModulesFactory::loadModule(
				const std::string& sModuleName,
				const std::string& sVersion,
				const std::string& sLibraryName) {
			boost::mutex::scoped_lock oScopedLock(m_mxLoadedModules);
			Modules::iterator it=m_aLoadedModules.find(sModuleName);
			if(it!=m_aLoadedModules.end()) {
				it->second->addRef();
				return it->second;
			}
			else {
				IModule::SharedPtr poSharedImplPtr = loadModuleFromLibrary(sModuleName, sVersion, sLibraryName);
				m_aLoadedModules.insert( std::pair<std::string, IModule::SharedPtr> (sModuleName, poSharedImplPtr));
				return poSharedImplPtr;
			}
		}

		// --------------------------------------------------------------------------------------------------
		void ModulesFactory::unloadModule(IModule::SharedPtr poModule) {
			boost::mutex::scoped_lock oScopedLock(m_mxLoadedModules);
			Modules::iterator it=m_aLoadedModules.find(poModule->getName());
			if(it!=m_aLoadedModules.end()) {
				if(it->second->unload())
					m_aLoadedModules.erase(it);
			}
		}

		// --------------------------------------------------------------------------------------------------
		void ModulesFactory::purgeAllModules() {
			boost::mutex::scoped_lock oScopedLock(m_mxLoadedModules);
			Modules aCopiedModules = m_aLoadedModules;
			m_aLoadedModules.clear();
			oScopedLock.unlock();
			for ( Modules::iterator it = aCopiedModules.begin(); it!=aCopiedModules.end();it++) {
				while(!it->second->unload());
			}
			aCopiedModules.clear();
		}
	}
}
